package com.codyy.felix.webconsole.plugins.route.core.provider;

import com.codyy.felix.webconsole.plugins.route.core.pojo.BundleJar;
import com.google.common.base.Function;
import com.google.common.base.Predicate;
import com.google.common.base.Predicates;
import com.google.common.base.Splitter;
import com.google.common.collect.FluentIterable;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.primitives.Longs;
import com.strobel.assembler.metadata.JarTypeLoader;
import com.strobel.decompiler.Decompiler;
import com.strobel.decompiler.DecompilerSettings;
import com.strobel.decompiler.PlainTextOutput;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.osgi.framework.*;
import org.osgi.service.cm.Configuration;
import org.osgi.service.cm.ConfigurationAdmin;
import org.osgi.service.metatype.MetaTypeInformation;
import org.osgi.service.metatype.MetaTypeService;
import org.osgi.service.metatype.ObjectClassDefinition;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.jar.JarFile;
import java.util.stream.Collectors;

public class OsgiExplorer {

    public static final String BUNDLE_STORAGE_PROP = "org.osgi.framework.storage";

    public static final String BUNDLE_STORAGE_DIR_DEFAULT = "felix-cache";

    public static final String BUNDLE_PATH_FORMAT = "%s/bundle%s";

    public static final String BUNDLE_JAR_FILE = "bundle.jar";

    public static final String JAR_EXT = "jar";

    private static final String DEPLOY_DIR = "deploy";

    private static final Logger LOG = LoggerFactory.getLogger(OsgiExplorer.class);

    private final MetaTypeService metaTypeService;

    private final ConfigurationAdmin configAdmin;

    private BundleContext context;

    public OsgiExplorer(BundleContext context) {
        this.context = context;
        this.metaTypeService = service(MetaTypeService.class);
        this.configAdmin = service(ConfigurationAdmin.class);
    }

    /**
     * 查找xml文件路径
     */
    public String findXmlPath() {
        return System.getProperty("user.dir") + File.separator + DEPLOY_DIR;
    }

    public File findDir(Long bundleId) {
        return new File(String.format(BUNDLE_PATH_FORMAT, getBundleDir(), bundleId));
    }

    private String getBundleDir() {
        return StringUtils.defaultIfBlank(context.getProperty(BUNDLE_STORAGE_PROP),
                System.getProperty("user.dir") + "/" + BUNDLE_STORAGE_DIR_DEFAULT);
    }



    public File findJar(String bundleId) {
        return findJar(Long.valueOf(bundleId));
    }

    public File findJar(Long bundleId) {
        return findJar(findDir(bundleId));
    }

    public File findJar(File bundleDir) {
        if (bundleDir.exists()) {
            List<File> files = FileUtils.listFiles(bundleDir, new String[]{JAR_EXT}, true)
                    .stream().filter(file -> file.getName().equalsIgnoreCase(BUNDLE_JAR_FILE))
                    .sorted((f1, f2) -> f2.getAbsolutePath().compareTo(f1.getAbsolutePath()))
                    .collect(Collectors.toList());

            return Iterables.getFirst(files, null);
        }

        return null;
    }

    public BundleJar findBundleJar(String bundleId) {
        BundleJar result = null;

        File jar = findJar(bundleId);
        Bundle bundle = findBundle(bundleId);

        if (jar != null && bundle != null) {
            result = new BundleJar(bundle, jar);
        }

        return result;
    }

    public Set<BundleJar> findBundleJars(List<String> bundleIds) {
        Set<BundleJar> result = Sets.newLinkedHashSet();

        for (String bundleId : bundleIds) {
            BundleJar jar = findBundleJar(bundleId);
            if (jar != null) {
                result.add(jar);
            }
        }

        return result;
    }

    public String proposeJarName(Long bundleId) {
        return proposeJarName(String.valueOf(bundleId));
    }

    public String proposeJarName(String bundleId) {
        String result = BUNDLE_JAR_FILE;

        final Bundle bundle = context.getBundle(Long.valueOf(bundleId));
        if (bundle != null) {
            final String realName = FilenameUtils.getName(bundle.getLocation());
            if (StringUtils.isNotEmpty(realName) && JAR_EXT.equalsIgnoreCase(FilenameUtils.getExtension(realName))) {
                result = realName;
            } else {
                result = String.format("%s.%s", bundle.getSymbolicName(), JAR_EXT);
            }
        }

        return result;
    }


    public Bundle findBundle(String id) {
        final Long longId = Longs.tryParse(id);

        return longId != null ? context.getBundle(longId) : null;
    }

    public Bundle findBundleByName(String name) {
        if (StringUtils.isBlank(name)) {
            return null;
        }
        Bundle[] bundles = context.getBundles();
        for (Bundle item : bundles) {
            if (name.equals(item.getSymbolicName())) {
                return item;
            }
        }
        return null;
    }

    public Iterable<Bundle> findBundles(List<String> ids) {
        return FluentIterable.from(ids).transform(new Function<String, Bundle>() {
            @Override
            public Bundle apply(String id) {
                return findBundle(id);
            }
        }).filter(Predicates.<Bundle>notNull());
    }

    public Bundle findBundle(final Configuration configuration) {
        return FluentIterable.from(context.getBundles()).firstMatch(new Predicate<Bundle>() {
            @Override
            public boolean apply(Bundle bundle) {
                return StringUtils.equals(configuration.getBundleLocation(), bundle.getLocation());
            }
        }).orNull();
    }

    public ObjectClassDefinition getClassDefinition(Bundle bundle, String pid) {
        ObjectClassDefinition result = null;

        if (bundle != null) {
            try {
                final MetaTypeInformation info = metaTypeService.getMetaTypeInformation(bundle);

                if (info != null) {
                    result = info.getObjectClassDefinition(pid, null);
                }
            } catch (Throwable e) { //NOSONAR
                // ignore
            }
        }

        return result;
    }

    @SuppressWarnings("unchecked")
    public <T> T service(Class<T> clazz) {
        ServiceReference reference = context.getServiceReference(clazz.getName());

        return reference != null ? (T) context.getService(reference) : null;
    }

    public Object findService(final String pid) {
        return referenceService(findServiceReference(pid));
    }

    public Object referenceService(ServiceReference reference) {
        Object service = null;
        if (reference != null) {
            try {
                service = context.getService(reference);
            } catch (NoClassDefFoundError e) { //NOSONAR
                // ignore classloading issues etc
            }
        }

        return service;
    }

    public ServiceReference findServiceReference(final String pid) {
        ServiceReference result = null;

        if (StringUtils.isNotBlank(pid)) {
            try {
                result = FluentIterable.from(context.getAllServiceReferences(null, null))
                        .firstMatch(new Predicate<ServiceReference>() {
                            @Override
                            public boolean apply(ServiceReference serviceReference) {
                                return StringUtils.equals(pid, (String) serviceReference.getProperty(Constants.SERVICE_PID));
                            }
                        }).orNull();
            } catch (InvalidSyntaxException e) { // NOSONAR
                // ignore
            }
        }

        return result;
    }

    public Iterable<ServiceReference> getServiceReferences() {
        try {
            return FluentIterable.from(context.getAllServiceReferences(null, null));
        } catch (InvalidSyntaxException e) {
            LOG.error("Cannot obtain references during service search.", e);
        }

        return Collections.emptyList();
    }

    public Iterable<Configuration> getConfigurations() {
        Iterable<Configuration> configurations = Collections.emptyList();

        if (configAdmin != null) {
            try {
                configurations = FluentIterable.from(configAdmin.listConfigurations(null));
            } catch (IOException | InvalidSyntaxException e) {
                LOG.error("Cannot read configurations", e);
            }
        }

        return configurations;
    }

    public BundleContext getContext() {
        return context;
    }
}
